package ru.testtask.dao.jdbc;

import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import ru.testtask.dao.XmlDao;
import ru.testtask.model.XmlDocument;
import ru.testtask.model.XmlElement;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;

public class JdbcXmlDao extends JdbcDaoSupport implements XmlDao {

    private NameIdXmlDocumentMapper nameIdXmlDocumentMapper;
    private XmlElementsRowMapper xmlElementsRowMapper;

    protected void initDao() throws Exception {
        nameIdXmlDocumentMapper = new NameIdXmlDocumentMapper();
        xmlElementsRowMapper = new XmlElementsRowMapper();
    }

    /**
     * this method may throw StackOverflowError in case of too deep elements tree. Because it uses recursion.
     * @param xmlDoc xml document to save
     */
    public void saveObjectModelBad(XmlDocument xmlDoc) {
        int xmlDocId = getJdbcTemplate().queryForInt("select nextval(?)", "xml_doc_seq");
        getJdbcTemplate().update("insert into xml_docs (id, name) values (?, ?)", xmlDocId, xmlDoc.getName());
        List<XmlElement> elements = xmlDoc.getContent();
        insertElements(xmlDocId, elements, 0);
    }

    private void insertElements(int xmlDocId, List<XmlElement> elements, int parentId) {
        if (elements == null) {
            return;
        }
        for (XmlElement el :elements) {
            int elementId = getJdbcTemplate().queryForInt("select nextval(?)", "xml_elem_seq");
            getJdbcTemplate().update(
                    "insert into xml_elements (id, doc_id, parent_id, elem_name, attr_name, attr_value) " +
                            "values (?, ?, ?, ?, ?, ?)",
                    elementId,
                    xmlDocId,
                    parentId,
                    el.getName(),
                    el.getAttribute().getName(),
                    el.getAttribute().getValue()
            );
            insertElements(xmlDocId, el.getChilds(), elementId);
        }
    }

    /**
     * this method might not throw StackOverflowError because it does not use recursion.
     * @param xmlDoc xml document to save
     */
    public void saveObjectModel(XmlDocument xmlDoc) {

        int xmlDocId = getJdbcTemplate().queryForInt("select nextval(?)", "xml_doc_seq");
        getJdbcTemplate().update("insert into xml_docs (id, name) values (?, ?)", xmlDocId, xmlDoc.getName());

        List<XmlElement> elements = xmlDoc.getContent();

        Iterator<XmlElement> iter = elements.iterator();

        Queue<Iterator<XmlElement>> stack = Collections.asLifoQueue(new LinkedList<Iterator<XmlElement>>());
        stack.add(iter);

        while ((iter = stack.peek()) != null) {
            boolean gotChilds = false;
            while (iter.hasNext()) {
                XmlElement element = iter.next();
                saveElement(xmlDocId, element);

                if (element.getChilds() != null) {
                    stack.add(element.getChilds().iterator());
                    gotChilds = true;
                    break;
                }
            }
            if (!gotChilds) {
                stack.remove();
            }
        }

    }

    private void saveElement(int xmlDocId, XmlElement el) {
        if (el == null) {
            return;
        }
        int elementId = getJdbcTemplate().queryForInt("select nextval(?)", "xml_elem_seq");
        int parentId = el.getParent() != null ? el.getParent().getId() : 0;

        getJdbcTemplate().update(
                "insert into xml_elements (id, doc_id, parent_id, level, elem_name, attr_name, attr_value) " +
                        "values (?, ?, ?, ?, ?, ?, ?)",
                elementId,
                xmlDocId,
                parentId,
                el.getLevel(),
                el.getName(),
                el.getAttribute().getName(),
                el.getAttribute().getValue()
        );
        el.setId(elementId);
    }


    public XmlDocument readObjectModel(int docId) {

        List<XmlDocument> lst = getJdbcTemplate().query("select id, name from xml_docs where id=?", new Object [] {docId}, nameIdXmlDocumentMapper);
        if (lst == null || lst.size() == 0) {
            return null;
        }
        XmlDocument xmlDoc = lst.get(0);

        List<XmlElement> elements = getJdbcTemplate().query("select id, parent_id, level, elem_name, attr_name, attr_value from xml_elements where doc_id=? order by id", new Object[] {docId}, xmlElementsRowMapper);
        xmlDoc.setContent(elements);
        return xmlDoc;
    }

    public List<XmlDocument> getDocumentsList() {
        return getJdbcTemplate().query("select id, name from xml_docs", nameIdXmlDocumentMapper);
    }

    private class NameIdXmlDocumentMapper implements RowMapper<XmlDocument> {

        public XmlDocument mapRow(ResultSet rs, int rowNum) throws SQLException {
            XmlDocument res = new XmlDocument();
            res.setId(rs.getInt("id"));
            res.setName(rs.getString("name"));
            return res;
        }
    }

    private class XmlElementsRowMapper implements RowMapper<XmlElement> {

        public XmlElement mapRow(ResultSet rs, int rowNum) throws SQLException {
            XmlElement element = new XmlElement(rs.getString("elem_name"));
            element.setId(rs.getInt("id"));
            element.setLevel(rs.getInt("level"));
            element.getAttribute().setName(rs.getString("attr_name"));
            element.getAttribute().setValue(rs.getString("attr_value"));
            int parentId = rs.getInt("parent_id");
            if (parentId > 0) {
                element.setParent(new XmlElement(parentId), false);
            }
            return element;
        }
    }

}
